//===--- TaskQueue.inc - Default serial TaskQueue ---------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains a platform-agnostic implementation of TaskQueue
/// using the functions from llvm/Support/Program.h.
///
/// \note The default implementation of TaskQueue does not support parallel
/// execution, nor does it support output buffering. As a result,
/// platform-specific implementations should be preferred.
///
//===----------------------------------------------------------------------===//

#include "swift/Basic/TaskQueue.h"

#include "swift/Basic/LLVM.h"

#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Signals.h"

#if defined(_WIN32)
#define NOMINMAX
#include <Windows.h>
#include <psapi.h>
#endif

using namespace llvm::sys;

namespace swift {
namespace sys {

class Task {
public:
  /// The path to the executable which this Task will execute.
  const char *ExecPath;

  /// Any arguments which should be passed during execution.
  ArrayRef<const char *> Args;

  /// The environment which should be used during execution. If empty,
  /// the current process's environment will be used instead.
  ArrayRef<const char *> Env;

  /// True if the errors of the Task should be stored in Errors instead of Output.
  bool SeparateErrors;

  /// Context associated with this Task.
  void *Context;

  Task(const char *ExecPath, ArrayRef<const char *> Args,
       ArrayRef<const char *> Env = llvm::None, void *Context = nullptr, bool SeparateErrors = false)
      : ExecPath(ExecPath), Args(Args), Env(Env), Context(Context), SeparateErrors(SeparateErrors) {}
};

} // end namespace sys
} // end namespace swift

bool TaskQueue::supportsBufferingOutput() {
  // The default implementation supports buffering output.
  return true;
}

bool TaskQueue::supportsParallelExecution() {
  // The default implementation does not support parallel execution.
  return false;
}

unsigned TaskQueue::getNumberOfParallelTasks() const {
  // The default implementation does not support parallel execution.
  return 1;
}

void TaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
                        ArrayRef<const char *> Env, void *Context,
                        bool SeparateErrors) {
  std::unique_ptr<Task> T(new Task(ExecPath, Args, Env, Context, SeparateErrors));
  QueuedTasks.push(std::move(T));
}

bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
                        TaskSignalledCallback Signalled) {
  bool ContinueExecution = true;

  // This implementation of TaskQueue doesn't support parallel execution.
  // We need to reference NumberOfParallelTasks to avoid warnings, though.
  (void)NumberOfParallelTasks;

  while (!QueuedTasks.empty() && ContinueExecution) {
    std::unique_ptr<Task> T(QueuedTasks.front().release());
    QueuedTasks.pop();

    SmallVector<const char *, 128> Argv;
    Argv.push_back(T->ExecPath);
    Argv.append(T->Args.begin(), T->Args.end());
    Argv.push_back(nullptr);

    llvm::Optional<llvm::ArrayRef<llvm::StringRef>> Envp =
        T->Env.empty() ? decltype(Envp)(None)
                       : decltype(Envp)(llvm::toStringRefArray(T->Env.data()));

    llvm::SmallString<64> stdoutPath;
    if (fs::createTemporaryFile("stdout", "tmp",  stdoutPath))
      return true;
    llvm::sys::RemoveFileOnSignal(stdoutPath);

    llvm::SmallString<64> stderrPath;
    if (T->SeparateErrors) {
      if (fs::createTemporaryFile("stderr", "tmp", stdoutPath))
        return true;
      llvm::sys::RemoveFileOnSignal(stderrPath);
    }

    Optional<StringRef> redirects[] = {None, {stdoutPath}, {T->SeparateErrors ? stderrPath : stdoutPath}};

    bool ExecutionFailed = false;
    ProcessInfo PI = ExecuteNoWait(T->ExecPath,
                                   llvm::toStringRefArray(Argv.data()), Envp,
                                   /*redirects*/redirects, /*memoryLimit*/0,
                                   /*ErrMsg*/nullptr, &ExecutionFailed);
    if (ExecutionFailed) {
      return true;
    }

    if (Began) {
      Began(PI.Pid, T->Context);
    }

    std::string ErrMsg;
    PI = Wait(PI, 0, true, &ErrMsg);
    int ReturnCode = PI.ReturnCode;

    auto stdoutBuffer = llvm::MemoryBuffer::getFile(stdoutPath);
    StringRef stdoutContents = stdoutBuffer.get()->getBuffer();

    StringRef stderrContents;
    if (T->SeparateErrors) {
      auto stderrBuffer = llvm::MemoryBuffer::getFile(stderrPath);
      stderrContents = stderrBuffer.get()->getBuffer();
    }

#if defined(_WIN32)
    // Wait() sets the upper two bits of the return code to indicate warnings
    // (10) and errors (11).
    //
    // This isn't a true signal on Windows, but we'll treat it as such so that
    // we clean up after it properly
    bool crashed = ReturnCode & 0xC0000000;

    FILETIME creationTime;
    FILETIME exitTime;
    FILETIME utimeTicks;
    FILETIME stimeTicks;
    PROCESS_MEMORY_COUNTERS counters = {};
    GetProcessTimes(PI.Process, &creationTime, &exitTime, &stimeTicks,
                    &utimeTicks);
    // Each tick is 100ns
    uint64_t utime =
        ((uint64_t)utimeTicks.dwHighDateTime << 32 | utimeTicks.dwLowDateTime) /
        10;
    uint64_t stime =
        ((uint64_t)stimeTicks.dwHighDateTime << 32 | stimeTicks.dwLowDateTime) /
        10;
    GetProcessMemoryInfo(PI.Process, &counters, sizeof(counters));

    TaskProcessInformation tpi(PI.Pid, utime, stime,
                               counters.PeakWorkingSetSize);
#else
      // Wait() returning a return code of -2 indicates the process received
      // a signal during execution.
    bool crashed = ReturnCode == -2;
    TaskProcessInformation tpi(PI.Pid);
#endif
    if (crashed) {
      if (Signalled) {
        TaskFinishedResponse Response =
            Signalled(PI.Pid, ErrMsg, stdoutContents, stderrContents, T->Context, ReturnCode, tpi);
        ContinueExecution = Response != TaskFinishedResponse::StopExecution;
      } else {
        // If we don't have a Signalled callback, unconditionally stop.
        ContinueExecution = false;
      }
    } else {
      // Wait() returned a normal return code, so just indicate that the task
      // finished.
      if (Finished) {
        TaskFinishedResponse Response = Finished(PI.Pid, PI.ReturnCode,
        stdoutContents, stderrContents, tpi, T->Context);
        ContinueExecution = Response != TaskFinishedResponse::StopExecution;
      } else if (PI.ReturnCode != 0) {
        ContinueExecution = false;
      }
    }
    llvm::sys::fs::remove(stdoutPath);
    if (T->SeparateErrors)
      llvm::sys::fs::remove(stderrPath);
  }

  return !ContinueExecution;
}
